package wechat

import (
	"context"
	"errors"
	"fmt"
	"github.com/wechatpay-apiv3/wechatpay-go/core"
	"github.com/wechatpay-apiv3/wechatpay-go/services/payments"
	"github.com/wechatpay-apiv3/wechatpay-go/services/payments/native"
	"jk-time/webook/payment/domain"
	"jk-time/webook/payment/events"
	"jk-time/webook/payment/repository"
	"jk-time/webook/pkg/logger"
	"time"
)

var errUnknownTransactionState = errors.New("未知的微信事务状态")

type NativePaymentService struct {
	svc      *native.NativeApiService
	producer events.Producer
	l        logger.Logger
	// 公众号ID
	appID string
	// 直连商户号
	mchID string
	// 支付通知回调url
	notifyURL string
	// 自己的支付记录
	repo repository.PaymentRepository

	//* SUCCESS：支付成功
	//* REFUND：转入退款
	//* NOTPAY：未支付
	//* CLOSED：已关闭
	//* REVOKED：已撤销（仅付款码支付会返回）
	//* USERPAYING：用户支付中（仅付款码支付会返回）
	//* PAYERROR：支付失败（仅付款码支付会返回）
	nativeCBTypeToStatus map[string]domain.PaymentStatus
}

func NewNativePaymentService(svc *native.NativeApiService, producer events.Producer,
	l logger.Logger, appID string, mchID string, notifyURL string,
	repo repository.PaymentRepository) *NativePaymentService {
	return &NativePaymentService{svc: svc, producer: producer, l: l, appID: appID, mchID: mchID,
		notifyURL: notifyURL, repo: repo,
		nativeCBTypeToStatus: map[string]domain.PaymentStatus{
			"SUCCESS":    domain.PaymentStatusSuccess,
			"REFUND":     domain.PaymentStatusRefund,
			"NOTPAY":     domain.PaymentStatusInit,
			"USERPAYING": domain.PaymentStatusInit,
			"CLOSED":     domain.PaymentStatusFailed,
			"REVOKED":    domain.PaymentStatusFailed,
			"PAYERROR":   domain.PaymentStatusFailed,
		}}
}

func (n *NativePaymentService) Prepay(ctx context.Context, pmt domain.Payment) (string, error) {
	err := n.repo.AddPayment(ctx, pmt)
	if err != nil {
		return "", err
	}
	resp, _, err := n.svc.Prepay(ctx, native.PrepayRequest{
		Appid:       core.String(n.appID),
		Mchid:       core.String(n.mchID),
		Description: core.String(pmt.Description),
		OutTradeNo:  core.String(pmt.BizTradeNO),
		TimeExpire:  core.Time(time.Now().Add(time.Minute * 30)),
		NotifyUrl:   core.String(n.notifyURL),
		Amount: &native.Amount{
			Currency: core.String(pmt.Amt.Currency),
			Total:    core.Int64(pmt.Amt.Total),
		},
	})
	if err != nil {
		return "", err
	}
	return *resp.CodeUrl, nil
}

// SyncWechatInfo 对账
func (n *NativePaymentService) SyncWechatInfo(ctx context.Context, bizTradeNO string) error {
	//商户订单号查询订单
	txn, _, err := n.svc.QueryOrderByOutTradeNo(ctx, native.QueryOrderByOutTradeNoRequest{
		OutTradeNo: core.String(bizTradeNO),
		Mchid:      core.String(n.mchID),
	})
	if err != nil {
		return err
	}
	return n.UpdateByTxn(ctx, txn)
}
func (n *NativePaymentService) UpdateByTxn(ctx context.Context, txn *payments.Transaction) error {
	status, ok := n.nativeCBTypeToStatus[*txn.TradeState]
	if !ok {
		return fmt.Errorf("%w,微信的状态是 %s", errUnknownTransactionState, *txn.TradeState)
	}
	// 更新数据库
	pmt := domain.Payment{
		// 微信过来的 transaction id
		TxnID:      *txn.TransactionId,
		BizTradeNO: *txn.OutTradeNo,
		Status:     status,
	}
	err := n.repo.UpdatePayment(ctx, pmt)
	if err != nil {
		return err
	}

	// 就要通知业务方了
	// 有些人的系统，会根据支付状态来决定要不要通知
	// 我要是发消息失败了怎么办？
	// 站在业务的角度，你是不是至少应该发成功一次
	err = n.producer.ProducePaymentEvent(ctx, events.PaymentEvent{
		BizTradeNO: pmt.BizTradeNO,
		Status:     pmt.Status.AsUint8(),
	})
	if err != nil {
		n.l.Error("发送支付事件到kafka失败", logger.Error(err),
			logger.String("biz_trade_no", pmt.BizTradeNO))
	}
	return nil
}
func (n *NativePaymentService) FindExpiredPayment(ctx context.Context, offset int,
	limit int, t time.Time) ([]domain.Payment, error) {
	return n.repo.FindExpiredPayment(ctx, offset, limit, t)
}
func (n *NativePaymentService) GetPayment(ctx context.Context, bizTradeNO string) (domain.Payment, error) {
	return n.repo.GetPayment(ctx, bizTradeNO)
}
